#help_index "Graphics/Windows BMP Files"

#define BMP_COLORS_NUM	16

class CFileBMP
{
  U16 type;
  U32 file_size;
  U32 reserved;
  U32 data_offset;

  U32 header_size;
  U32 width;
  U32 height;
  U16 planes;
  U16 bit_cnt;
  U32 compression;
  U32 image_size;
  U32 x_pixs_per_meter;
  U32 y_pixs_per_meter;
  U32 colors_used;
  U32 important_colors;

  U0 end;

  CBGR24 palette[BMP_COLORS_NUM];
};

public CFileBMP *BMP4To(CDC *dc)
{//To Windows 4-bit BMP.
  U8 *src,*ptr;
  CBGR48 palette[COLORS_NUM];
  I64 i,x,y,w=dc->width>>1,
	size=sizeof(CFileBMP)+dc->width*dc->height>>1;
  CFileBMP *bmp	=CAlloc(size);
  bmp->type	='BM';
  bmp->planes	=1;
  bmp->file_size=size;
  bmp->data_offset=sizeof(CFileBMP);
  bmp->header_size=offset(CFileBMP.end)-offset(CFileBMP.header_size);
  bmp->width	=dc->width;
  bmp->height	=dc->height;
  bmp->bit_cnt	=4;
  bmp->image_size=dc->width*dc->height>>1;
  GrPaletteGet(palette);
#assert COLORS_NUM==BMP_COLORS_NUM
  for (i=0;i<BMP_COLORS_NUM;i++) {
    bmp->palette[i].b=palette[i].b>>8;
    bmp->palette[i].g=palette[i].g>>8;
    bmp->palette[i].r=palette[i].r>>8;
    bmp->palette[i].pad=0;
  }
  ptr=bmp(U8 *)+bmp->data_offset;
  for (y=dc->height-1;y>=0;y--) {
    src=y*dc->width_internal+dc->body;
    for (x=0;x<w;x++) {
      *ptr|=(*src++&15)<<4;
      *ptr|=*src++&15;
      ptr++;
    }
  }
  return bmp;
}

public CFileBMP *BMPRLE4To(CDC *dc)
{//To Windows RLE4 bit BMP.
  U8 *src,*ptr,*start;
  I64 i,x,y,w=dc->width,cnt,pattern;
  CBGR48 palette[COLORS_NUM];
  CFileBMP *bmp	=CAlloc(sizeof(CFileBMP)+2*(dc->width+1)*dc->height);
  bmp->type	='BM';
  bmp->planes	=1;
  bmp->data_offset=sizeof(CFileBMP);
  bmp->header_size=offset(CFileBMP.end)-offset(CFileBMP.header_size);
  bmp->width	=dc->width;
  bmp->height	=dc->height;
  bmp->bit_cnt	=4;
  bmp->compression=2; //RLE4
  GrPaletteGet(palette);
#assert COLORS_NUM==BMP_COLORS_NUM
  for (i=0;i<BMP_COLORS_NUM;i++) {
    bmp->palette[i].b=palette[i].b>>8;
    bmp->palette[i].g=palette[i].g>>8;
    bmp->palette[i].r=palette[i].r>>8;
    bmp->palette[i].pad=0;
  }
  start=ptr=bmp(U8 *)+bmp->data_offset;
  for (y=dc->height-1;y>=0;y--) {
    src=y*dc->width_internal+dc->body;
    x=0;
    while (x<w) {
      pattern=(src[0]&15)<<4+src[1]&15;
      if (x+1<w && src[0]&15==src[1]&15) {
	src+=2;
	cnt=2;
	x+=2;
	while (x<w && cnt<U8_MAX) {
	  if (*src&15==pattern&15) {
	    src++;
	    cnt++;
	    x++;
	  } else
	    break;
	}
      } else {
	src+=2;
	if (x+1<w)
	  cnt=2;
	else
	  cnt=1;
	x+=2;
      }
      *ptr++=cnt;
      *ptr++=pattern;
    }
    *ptr(U16 *)++=0;
  }
  bmp->image_size=ptr-start;
  bmp->file_size=sizeof(CFileBMP)+bmp->image_size;
  return bmp;
}

public CFileBMP *BMP24To(CDC *dc)
{//To Windows 24-bit BMP.
  U8 *src;
  I64 i,x,y,size=offset(CFileBMP.end)+dc->width*dc->height*sizeof(CBGR24);
  CBGR24 *bgr;
  CFileBMP *bmp	=CAlloc(size);
  bmp->type	='BM';
  bmp->planes	=1;
  bmp->file_size=size;
  bmp->data_offset=offset(CFileBMP.end);
  bmp->header_size=offset(CFileBMP.end)-offset(CFileBMP.header_size);
  bmp->width	=dc->width;
  bmp->height	=dc->height;
  bmp->bit_cnt	=32;
  bmp->image_size=dc->width*dc->height<<2;

  bgr=bmp(U8 *)+bmp->data_offset;
  for (y=dc->height-1;y>=0;y--) {
    src=y*dc->width_internal+dc->body;
    for (x=0;x<dc->width;x++) {
      i=*src++;
      if (i&BLUE)  bgr->b=0x7F;
      if (i&GREEN) bgr->g=0x7F;
      if (i&RED)   bgr->r=0x7F;
      if (i&8) {
	if (bgr->b) bgr->b=0xFF;
	if (bgr->g) bgr->g=0xFF;
	if (bgr->r) bgr->r=0xFF;
      }
      bgr(U8 *)+=4;
    }
  }
  return bmp;
}

public I64 BMPWrite(U8 *filename,CDC *dc,I64 bits=4)
{//Window's BMP Files.
  I64 size;
  CFileBMP *bmp;
  if (bits==4) {
    if (IsDotZ(filename)) //.Z compression is better than RLE
      bmp=BMP4To(dc);
    else {
      bmp=BMPRLE4To(dc);
      if (bmp->file_size>sizeof(CFileBMP)+dc->width*dc->height>>1) {
	Free(bmp);
	bmp=BMP4To(dc);
      }
    }
  } else if (bits==24)
    bmp=BMP24To(dc);
  else {
    "Format Not Supported.\n";
    return 0;
  }
  size=bmp->file_size;
  FileWrite(filename,bmp,bmp->file_size);
  Free(bmp);
  return size;
}

U8 *BMPPaletteNew(CFileBMP *bmp)
{
  I64 i,j,best,score,best_score;
  CBGR48 palette[COLORS_NUM];
  U8 *res=CAlloc(BMP_COLORS_NUM*sizeof(U8));
  GrPaletteGet(palette);
#assert COLORS_NUM==BMP_COLORS_NUM
  for (i=0;i<BMP_COLORS_NUM;i++) {
    best=i;
    best_score=I64_MAX;
    for (j=0;j<BMP_COLORS_NUM;j++) {
      score=SqrI64(bmp->palette[i].r-palette[j].r>>8)+
	    SqrI64(bmp->palette[i].g-palette[j].g>>8)+
	    SqrI64(bmp->palette[i].b-palette[j].b>>8);
      if (score<best_score) {
	best=j;
	best_score=score;
      }
    }
    res[i]=best;
  }
  return res;
}

U8 ms_paint_palette[BMP_COLORS_NUM]={0,4,2,6,1,5,3,8,7,12,10,14,9,13,11,15};

I64 BMP24Color(CBGR24 *ptr,Bool dither_probability)
{
  I64 res,k;
  if (dither_probability) {
    k=RandU32;
    if (SqrI64(ptr->r)+SqrI64(ptr->g)+SqrI64(ptr->b)>=3*SqrI64(k.u8[0]))
      res=8;
    else
      res=0;
    if (ptr->r>=k.u8[1]) res|=RED;
    if (ptr->g>=k.u8[2]) res|=GREEN;
    if (ptr->b>=k.u8[3]) res|=BLUE;
  } else {
    if (SqrI64(ptr->r)+SqrI64(ptr->g)+SqrI64(ptr->b)>=SqrI64(0x80)) {
      res=8;
      if (ptr->r>=0x80) res|=RED;
      if (ptr->g>=0x80) res|=GREEN;
      if (ptr->b>=0x80) res|=BLUE;
    } else {
      res=0;
      if (ptr->r>=0x40) res|=RED;
      if (ptr->g>=0x40) res|=GREEN;
      if (ptr->b>=0x40) res|=BLUE;
    }
  }
  return res;
}

public CDC *BMPRead(U8 *filename,Bool dither_probability=FALSE,
	Bool use_ms_paint_palette=FALSE)
{//Window's BMP Files.
  I64 i,j,cnt;
  U8 *palette_map,*ptr;
  Bool rle;
  CFileBMP *bmp;
  CDC *res=NULL;
  if (ptr=FileRead(filename)) {
    bmp=ptr;
    if (0<bmp->width<I32_MAX && 0<bmp->height<I32_MAX) {
      res=DCNew(bmp->width,bmp->height);
      ptr+=bmp->data_offset;
      if (bmp->compression==2)
	rle=TRUE;
      else
	rle=FALSE;
      if (use_ms_paint_palette)
	palette_map=ms_paint_palette;
      else
	palette_map=BMPPaletteNew(bmp);
      if (bmp->bit_cnt==4) {
	for (i=bmp->height-1;i>=0;i--)
	  if (rle) {//We don't support full RLE4, just our own subset
	    j=0;
	    while (cnt=*ptr++) {
	      if (cnt==1) {
		res->color=palette_map[*ptr++&15];
		GrPlot(res,j++,i);
	      } else {
		if (cnt==2 && *ptr>>4!=*ptr&15) {
		  res->color=palette_map[*ptr&15];
		  GrPlot(res,j+1,i);
		  res->color=palette_map[*ptr>>4];
		  GrPlot(res,j,i);
		  ptr++;
		  j+=2;
		} else {
		  res->color=palette_map[*ptr++&15];
		  while (cnt--)
		    GrPlot(res,j++,i);
		}
	      }
	    }
	    ptr++;
	  } else
	    for (j=0;j<(bmp->width+7)&~7;) {
	      res->color=palette_map[*ptr&15];
	      GrPlot(res,j+1,i);
	      res->color=palette_map[*ptr>>4];
	      GrPlot(res,j,i);
	      ptr++;
	      j+=2;
	    }
	if (!use_ms_paint_palette)
	  Free(palette_map);
      } else if (bmp->bit_cnt==24) {
	for (i=bmp->height-1;i>=0;i--) {
	  for (j=0;j<bmp->width;j++,ptr+=3) {
	    res->color=BMP24Color(ptr,dither_probability);
	    GrPlot(res,j,i);
	  }
	  ptr+=bmp->width&3;
	}
	if (!use_ms_paint_palette)
	  Free(palette_map);
      } else if (bmp->bit_cnt>=32) {
	for (i=bmp->height-1;i>=0;i--)
	  for (j=0;j<bmp->width;j++,ptr+=4) {
	    res->color=BMP24Color(ptr,dither_probability);
	    GrPlot(res,j,i);
	  }
	if (!use_ms_paint_palette)
	  Free(palette_map);
      } else {
	"Format Not Supported.\n";
	DCDel(res);
	res=NULL;
      }
    } else
      "Invalid BMP File\n";
    Free(bmp);
  }
  return res;
}

#help_index "Graphics/Sprite;Graphics/Windows BMP Files;"\
	"DolDoc/Output;StdOut/DolDoc"
public U0 DocBMP(CDoc *doc=NULL,U8 *filename,Bool dither_probability=FALSE,
	Bool use_ms_paint_palette=FALSE)
{//Put a BMP file into a document as a sprite.
  CDC *dc=BMPRead(filename,dither_probability,use_ms_paint_palette);
  U8 *elems=DC2Sprite(dc);
  DocSprite(doc,elems);
  Free(elems);
  DCDel(dc);
}

#help_index "Graphics/Windows BMP Files;Graphics/Scrn"
public I64 BMPScrnCapture(U8 *filename,I64 bits=4,Bool include_zoom=TRUE)
{//Capture scrn as BMP file.
  CDC *dc=DCScrnCapture(include_zoom);
  I64 size=BMPWrite(filename,dc,bits);
  DCDel(dc);
  return size;
}

public I64 GR2BMPLst(U8 *files_find_mask,U8 *fu_flags=NULL,
	U8 *out_print_fmt="~:/Tmp/VID%05d.BMP.Z",F64 fps=30000.0/1001)
{/*Cvt movie from GR lst to BMP lst
"+d" will delete GRLst files.
*/
  I64 res=0,fuf_flags=0;
  CDirEntry *tmpde,*tmpde1;
  CDC *dc,*base=DCNew(GR_WIDTH,GR_HEIGHT);
  U8 *st,*last_st;
  CDate in_cdt,out_cdt=I64_MIN;
  Bool old_silent;
  ScanFlags(&fuf_flags,Define("ST_FILE_UTIL_FLAGS"),"+f+F");
  ScanFlags(&fuf_flags,Define("ST_FILE_UTIL_FLAGS"),fu_flags);
  tmpde=tmpde1=FilesFind(files_find_mask,fuf_flags&FUG_FILES_FIND);
  last_st=MStrPrint(out_print_fmt,0);
  progress1_max=LinkedLstCnt(tmpde);
  while (tmpde) {
    dc=GRRead(tmpde->full_name);
    GrBlot(base,dc->x0,dc->y0,dc);
    in_cdt=Str2I64(tmpde->name,16);
    if (out_cdt==I64_MIN)
      out_cdt=in_cdt;
    while (out_cdt<=in_cdt) {
      st=MStrPrint(out_print_fmt,res++);
      BMPWrite(st,base);
      Free(st);
      out_cdt+=CDATE_FREQ/fps;
    }
    if (fuf_flags&FUF_DEL) {
      old_silent=Silent;
      Del(tmpde->full_name);
      Silent(old_silent);
    }
    DCDel(dc);
    progress1++;
    tmpde=tmpde->next;
  }
  progress1=progress1_max=0;
  DirTreeDel(tmpde1);
  Free(last_st);
  DCDel(base);
  return res;
}
